package tech.anzhen.simple.rpc.support.rest;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import tech.anzhen.simple.rpc.support.exception.ApplicationException;
import tech.anzhen.simple.rpc.support.exception.BaseException;
import tech.anzhen.simple.rpc.support.logging.LoggingTracer;
import tech.anzhen.simple.rpc.support.runtime.ModuleException;
import tech.anzhen.simple.rpc.support.runtime.Remote;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;


@Slf4j
@Controller
public abstract class RestController {

    protected static final String MIME = "application/json";

    private Gson gson = RestUtils.buildGson();

    private Type template = new TypeToken<RequestMessage<JsonElement>>() {}.getType();

    private ListableBeanFactory beanFactory;


    private HashMap<String, RestServiceHolder> servicesMapping = new HashMap<>();

    public RestController(ListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        if (null != AnnotationUtils.findAnnotation(getClass(), RequestMapping.class)) {
            throw new BaseException("不允许在" + RestController.class.getName() + "的子类设置@RequestMapping");
        }
        autoScan(beanFactory);
    }

    @PostConstruct
    public void afterPropertiesSet() {
        log.info("init rest controller");

    }

    public void doExecute(NativeWebRequest request, HttpServletResponse response, OutputStream out, String facade, String method) throws Throwable {

        long now = System.currentTimeMillis();

        String name = facade + "/" + method;
        RestServiceHolder holder = servicesMapping.get(name);
        if (holder == null) {
            throw new BaseException("REST服务不存在:" + name);
        }

        RequestMessage<JsonElement> param = toRequestMessage(request, holder);

        Object[] p = new Object[]{};
        if (holder.getMethod().getGenericParameterTypes().length > 0) {
            p = new Object[]{gson.fromJson(param.getData(), holder.getMethod().getGenericParameterTypes()[0])};
        }

        Throwable ex = null;
        Object result;
        ResponseMessageBuilder builder = ResponseMessageBuilder.custom();
        try {
            result = holder.getMethod().invoke(holder.getService(), p);
            builder.data(result);
        } catch (InvocationTargetException e) {
            ex = e.getCause();
        } catch (Exception e) {
            ex = e;
        }

        if (ex != null) {
            log.info(ex.getMessage(), ex);
            if (ex instanceof ApplicationException) {
                builder.code(RestErrorCodes.APPLICATION_ERROR);
            } else if (ex instanceof ModuleException) {
                builder.code(RestErrorCodes.MODEL_ERROR);
                builder.fail().moduleCode(((ModuleException) ex).getModuleCode()).stack(ex);
                builder.fail().moduleErrorCode(((ModuleException) ex).getModuleErrorCode()).stack(ex);
            } else {
                builder.code(RestErrorCodes.ERROR);
            }

            builder.fail().message(ex.getMessage()).stack(ex);

        }

        ResponseMessage resp = builder.build();

        log.info(
                "complete request,requestURI={},time={},success={},code={},moduleCode={},moduleErrorCode={},message={}",
                name, System.currentTimeMillis() - now, resp.isSuccess(), resp.getCode(), resp.getModuleCode(),
                resp.getModuleErrorCode(), resp.getMessage());
        String s = gson.toJson(resp);
        IOUtils.copy(new StringReader(s), out, StandardCharsets.UTF_8);
    }

    @RequestMapping(value = "api/rest/{service}/{method}")
    public void execute(NativeWebRequest request, HttpServletResponse response, OutputStream out, @PathVariable String service, @PathVariable String method) throws Throwable {

        try {
            String tracer = request.getHeader(RestUtils.X_REST_TRACER);
            if (!StringUtils.isEmpty(tracer)) {
                LoggingTracer.set(tracer);
            } else {
                LoggingTracer.start();
            }

            doExecute(request, response, out, service, method);
        } finally {
            LoggingTracer.remove();
        }
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseMessage handleException(Exception ex) {

        log.error("服务器错误:" + ex.getClass().getName() + ",message=" + ex.getMessage(), ex);

        return ResponseMessageBuilder.custom().fail().code("1").message(ex.getMessage()).stack(ex).build();
    }

    private void autoScan(ListableBeanFactory listableBeanFactory) {
        String[] beanNames = listableBeanFactory.getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            if ("restController".equals(beanName)) {
                continue;
            }
            Object bean = listableBeanFactory.getBean(beanName);
            Class<?> beanType = AopUtils.getTargetClass(bean);

            if (!beanType.isAnnotationPresent(Remote.class)) {
                continue;
            }
            if (beanType.isInterface()) {
                continue;
            }
            Remote remote = beanType.getAnnotation(Remote.class);

            for (Class<?> iface : remote.interfaces()) {
                if (!iface.isInterface()) {
                    throw new BaseException("@Remote的interfaces不正确," + iface + "不是一个interface");
                }
                for (Method m : iface.getMethods()) {
                    register(iface, bean, m);
                }
            }
        }

        if (listableBeanFactory instanceof HierarchicalBeanFactory) {
            HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) listableBeanFactory;
            if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
                autoScan((ListableBeanFactory) hbf.getParentBeanFactory());
            }
        }
    }

    private RequestMessage<JsonElement> toRequestMessage(NativeWebRequest request, RestServiceHolder holder)
            throws IOException {

        InputStream input = (InputStream) new SmartInvoker().invoke(request.getNativeRequest(), "getInputStream", null);

        byte[] data = IOUtils.toByteArray(input);


        String json = IOUtils.toString(data, "utf-8");
        log.info("received json:" + json);
        if (StringUtils.isEmpty(json)) {
            throw new BaseException("报文数据为空");
        }

        RequestMessage<JsonElement> param;
        try {
            param = gson.fromJson(json, template);
        } catch (JsonSyntaxException e) {
            throw new BaseException("报文格式不正确", e);
        }
        return param;
    }

    private void register(Class<?> iface, Object bean, Method method) {

        RestServiceHolder holder = new RestServiceHolder();
        holder.setIface(iface);
        holder.setService(bean);
        holder.setMethod(method);

//		String root = RestUtils.pathAppend("/api/rest", iface.getSimpleName());
//		String sub = RestUtils.javaNameToRest(method.getName());
//		String mapping = RestUtils.pathAppend(root, sub);
//		register(mapping, holder);

        String mapping = iface.getSimpleName() + "/" + method.getName();
        if (!servicesMapping.containsKey(mapping)) {
            register(mapping, holder);
        }
    }

    private void register(String mapping, RestServiceHolder holder) {
        if (servicesMapping.containsKey(mapping)) {
            log.info("地址:" + mapping + "重复映射");
            return;
        }
        servicesMapping.put(mapping, holder);
        log.info("mapping rest service:" + mapping);
    }

    private static class RestServiceHolder {

        private Class<?> iface;

        private Object service;

        private Method method;

        public Class<?> getIface() {
            return iface;
        }

        public void setIface(Class<?> iface) {
            this.iface = iface;
        }

        public Object getService() {
            return service;
        }

        public void setService(Object service) {
            this.service = service;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

    }
}
